package com.cz.video.framework.interceptor;

import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.exception.BackResultException;
import cn.dev33.satoken.exception.StopMatchException;
import cn.dev33.satoken.fun.SaParamFunction;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.cz.video.common.constant.CacheNames;
import com.cz.video.common.core.domain.entity.SysUser;
import com.cz.video.common.core.service.UserService;
import com.cz.video.common.helper.LoginHelper;
import com.cz.video.common.utils.CacheHelper;
import com.cz.video.common.utils.DateUtils;
import com.cz.video.common.utils.redis.RedisUtils;
import com.cz.video.common.utils.spring.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RSet;
import org.springframework.web.method.HandlerMethod;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * {@code @Description} : sa校验的拦截器
 * {@code @Author} : 陈智
 * {@code @Date}: 2023/9/21  14:16
 */
@Slf4j
public class SaInterceptorPlus extends SaInterceptor {

//    private final static UserService userService = SpringUtils.getBean(UserService.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        try {

            if(isAnnotation && handler instanceof HandlerMethod) {

                // 获取此请求对应的 Method 处理函数
                Method method = ((HandlerMethod) handler).getMethod();

                // 如果此 Method 或其所属 Class 标注了 @SaIgnore，则忽略掉鉴权
                if(SaStrategy.me.isAnnotationPresent.apply(method, SaIgnore.class)|| LoginHelper.isAdmin()) {
                    return true;
                }
                // 注解校验
                SaStrategy.me.checkMethodAnnotation.accept(method);
            }

            // Auth 校验
            auth.run(handler);
        } catch (StopMatchException e) {
            // 停止匹配，进入Controller
        } catch (BackResultException e) {
            // 停止匹配，向前端输出结果
            if(response.getContentType() == null) {
                response.setContentType("text/plain; charset=utf-8");
            }
            response.getWriter().print(e.getMessage());
            return false;
        }
        //记录访问时间等等
        if (StpUtil.isLogin()){
            refreshVisitInfo();
        }
        // 通过验证
        return true;
    }

    private void refreshActivityDays() {
        //如果该用户当天没有访问过
        String key = CacheNames.ACTIVITY_DAYS_FLAG;
        RSet<Object> set = RedisUtils.getClient().getSet(key);
        if (set.contains(LoginHelper.getUserId()))
            return;
        //获取锁
        RLock lock = RedisUtils.getClient().getLock(CacheNames.ACTIVITY_DAYS_LOCK + ":" + LoginHelper.getUserId());
        try {
            boolean tryLock = lock.tryLock(0,5, TimeUnit.SECONDS);
            if (tryLock){
                //二次查询
                if (set.contains(LoginHelper.getUserId()))
                    return;
                //如果不存在该标志，立马刷新到数据库。TODO:cz:是否要考虑redis中这个数据丢失的情况
//                userService.updateActivityDays(LoginHelper.getUserId());
                //添加刷新标志
                DateTime nowDate = DateUtil.date();
                set.add(LoginHelper.getUserId());
                set.expireIfNotSetAsync(Duration.ofSeconds(DateUtil.between(nowDate,DateUtil.endOfDay(nowDate), DateUnit.SECOND)));
            }
        }catch (Exception e){
            log.error("获取锁异常", e);
        }finally {
            lock.forceUnlock();
        }
    }

    /**
     * 刷新访问信息（现在是刷新前台用户的上一次访问时间和活跃日期这些）
     */
    private void refreshVisitInfo() {
        //查询当前登录用户的上一次访问时间
        String key = CacheHelper.getRealName(CacheNames.USER_VISIT_INFO);
        SysUser userVisitInfo = RedisUtils.getCacheMapValue(key, String.valueOf(LoginHelper.getUserId()));
        //如果没有就添加这个
        Date nowDate = DateUtils.getNowDate();
        if (userVisitInfo==null){
            //尝试刷新一下活跃天数
            refreshActivityDays();
            userVisitInfo=new SysUser();
            userVisitInfo.setUserId(LoginHelper.getUserId());
            userVisitInfo.setLastOnlineTime(nowDate);
            //设置数据
            RedisUtils.setCacheMapValue(key,String.valueOf(LoginHelper.getUserId()), userVisitInfo);
            RedisUtils.getClient().getScoredSortedSet(CacheNames.USER_VISIT_TIME_SERIES).add((double) nowDate.getTime(),String.valueOf(userVisitInfo.getUserId()));
        }else {
            //当userVisitInfo存在时
            //如果上次访问时间不是今天就尝试刷新一下活跃天数
            if (!DateUtil.isSameDay(userVisitInfo.getLastOnlineTime(),nowDate))
                refreshActivityDays();
            userVisitInfo.setLastOnlineTime(nowDate);
            //设置数据
            RedisUtils.setCacheMapValue(key,String.valueOf(LoginHelper.getUserId()), userVisitInfo);
            RedisUtils.getClient().getScoredSortedSet(CacheNames.USER_VISIT_TIME_SERIES).add((double) nowDate.getTime(),String.valueOf(userVisitInfo.getUserId()));
        }
    }

    public SaInterceptorPlus(SaParamFunction<Object> auth) {
        super(auth);
    }

}
